package com.cmes.mes.audit.service;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.cmes.core.exception.FrameworkException;
import com.cmes.core.utils.DateUtils;
import com.cmes.core.utils.StringPool;
import com.cmes.core.utils.StringUtils;
import com.cmes.jdbc.service.GenericService;
import com.cmes.mes.audit.entity.SnGenerateEntity;
import com.cmes.mes.audit.entity.SnGenerateShardEntity;
import com.cmes.mes.audit.repository.SnGenerateShardRepository;
import com.google.common.collect.Maps;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * 编号生成器
 *
 * @author Bruce.Gong
 * @since 1.0.0-SNAPSHOT
 */
@Slf4j
@RequiredArgsConstructor
@Service
@Transactional(propagation = Propagation.SUPPORTS, rollbackFor = Exception.class)
public class SnGenerateShardService extends GenericService<SnGenerateShardRepository, SnGenerateShardEntity> {

    /**
     * 生成编码
     */
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public String generateSn(SnGenerateEntity generator, LocalDate date) {
        String dateStr = DateUtils.format(date);
        return generateSn(generator, getNextSn(generator.getBusinessCode(), dateStr, 1, 0));
    }

    /**
     * 生成编码
     */
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public List<String> generateSn(SnGenerateEntity generator, LocalDate date, int count) {
        String dateStr = DateUtils.format(date);
        return generateSn(generator, getNextSn(generator.getBusinessCode(), dateStr, count, 0), count);
    }

    private List<String> generateSn(SnGenerateEntity generator, Long sn, int count) {
        return IntStream.range(0, count).boxed().map(v -> generateSn(generator, sn - v)).collect(Collectors.toList());
    }

    private String generateSn(SnGenerateEntity generator, Long sn) {
        Map<String, String> params = Maps.newHashMapWithExpectedSize(1);
        String snStr = generator.getSnLength() > 0 ? StringUtils.leftPad(sn.toString(), generator.getSnLength(), StringPool.ZERO) : sn.toString();
        params.put("sn", snStr);
        return StringUtils.replacePlaceholder(generator.getRule(), params);
    }

    /**
     * 获取下一个Sn号，默认重试三次
     */
    private Long getNextSn(String businessCode, String shard, int count, int retryTimes) {
        SnGenerateShardEntity db = getOne(Wrappers.<SnGenerateShardEntity>lambdaQuery()
                .eq(SnGenerateShardEntity::getBusinessCode, businessCode)
                .eq(SnGenerateShardEntity::getShard, shard));
        Long maxSn = 0L;
        try {
            if (db == null) {
                Long nextSn = maxSn + count;
                insert(SnGenerateShardEntity.builder()
                        .businessCode(businessCode)
                        .shard(shard)
                        .maxSn(nextSn)
                        .build());
                return nextSn;
            } else {
                maxSn = db.getMaxSn();
                Long nextSn = maxSn + count;
                boolean result = update(Wrappers.<SnGenerateShardEntity>lambdaUpdate()
                        .set(SnGenerateShardEntity::getMaxSn, nextSn)
                        .eq(SnGenerateShardEntity::getId, db.getId())
                        .eq(SnGenerateShardEntity::getMaxSn, maxSn));
                if (!result) {
                    throw new FrameworkException("自动生成编码失败，该编码已经被其它用户占用，请稍后再操作");
                } else {
                    return nextSn;
                }
            }
        } catch (Exception e) {
            if (retryTimes >= 3) {
                throw new FrameworkException("自动生成编码失败，该编码已经被其它用户占用，请稍后再操作");
            } else {
                return getNextSn(businessCode, shard, count, retryTimes + 1);
            }
        }
    }

}
